#include "MersenneTwister.h"

MersenneTwister::MersenneTwister(cl_uint count)
{
	width = count;
	initializeHost();
	initializeCL();
	runCLKernels();
}
MersenneTwister::~MersenneTwister()
{
	cleanupCL();
	cleanupHost();
}

void MersenneTwister::initializeHost(void)
{
	randomValue			= NULL;
	mtConfigs			= NULL;
	
	randomValue = (cl_float *) malloc(width * sizeof(cl_float));
	if(randomValue == NULL)
	{
		printf("Error: Failed to allocate randomValue memory on host\n");
		return; 
	}

	srand((unsigned int) (time(NULL)));
	mtConfigs = (MersenneTwisterConfig*) malloc(width * sizeof(MersenneTwisterConfig));
	for(unsigned int i=0; i<width; i++)
	{
		double num = (rand() / (float) RAND_MAX) * 4194304;
		mtConfigs[i].mask_b = (unsigned int) num;
		mtConfigs[i].mask_c = (unsigned int) num;
		mtConfigs[i].matrix_a = (unsigned int) num;
		mtConfigs[i].seed = (unsigned int) num;
	}
	if(mtConfigs == NULL)
	{
		printf("Error: Failed to allocate 'MersenneTwisterConfig' memory on host\n");
		return; 
	}
}
string MersenneTwister::convertToString(const char * filename)
{
size_t size;
	char * str;
	string code;

	fstream file(filename, (fstream::in | fstream::binary));

	if(file.is_open())
	{
		size_t fileSize;
		file.seekg(0, fstream::end);
		size=fileSize=file.tellg();
		file.seekg(0, fstream::beg);

		str = new char[size+1];
		if(!str)
		{
			file.close();
			return NULL;
		}

		file.read(str, fileSize);
		file.close();
		str[size] = '\0';

		code = str;

		return code;
	}
	return NULL;
}

// OpenCL related initializations are done here.
// Context, Device list, Command Queue are set up.
// Calls are made to set up OpenCL memory buffers that this program uses
// and to load the programs into memory and get kernel handles.
void MersenneTwister::initializeCL(void)
{
	cl_int status = 0;
    size_t deviceListSize;

    // Have a look at the available platforms and pick either
    // the AMD one if available or a reasonable default.

    cl_uint numPlatforms;
    cl_platform_id platform = NULL;
	// obtain the list of platforms available.
    status = clGetPlatformIDs(0, NULL, &numPlatforms);
    if(status != CL_SUCCESS)
    {
        printf("Error: Getting Platforms.\n");
		printError(status, "clGetPlatformsIDs");
        return;
    }
    
    if(numPlatforms > 0)
    {
        cl_platform_id* platforms = (cl_platform_id *)malloc(numPlatforms*sizeof(cl_platform_id));
        status = clGetPlatformIDs(numPlatforms, platforms, NULL);
        if(status != CL_SUCCESS)
        {
            printf("Error: Getting Platform Ids.\n");
			printError(status, "clGetPlatformsIDs");
            return;
        }
        for(unsigned int i=0; i < numPlatforms; ++i)
        {
            char pbuff[100];
			// get specific information about the OpenCL platform
            status = clGetPlatformInfo(
                        platforms[i],
                        CL_PLATFORM_VENDOR,
                        sizeof(pbuff),
                        pbuff,
                        NULL);
            if(status != CL_SUCCESS)
            {
                printf("Error: Getting Platform Info.\n");
				printError(status, "clGetPlatformInfo");
                return;
            }
            platform = platforms[i];
            if(!strcmp(pbuff, "Advanced Micro Devices, Inc."))
            {
                break;
            }
        }
        delete platforms;
    }

    if(NULL == platform)
    {
        cout << "NULL platform found so Exiting Application." << endl;
        return;
    }

    
    //If we could find our platform, use it. Otherwise use just available platform.
    
    cl_context_properties cps[3] = { CL_CONTEXT_PLATFORM, (cl_context_properties)platform, 0 };


	
	// Create an OpenCL context
	context = clCreateContextFromType(cps, 
                                      CL_DEVICE_TYPE_GPU,
                                      NULL, 
                                      NULL, 
                                      &status);
    if(status != CL_SUCCESS) 
	{  
		printf("Error: Creating Context.\n");
		printError(status, "clCreateContextFromType");
		return; 
	}

    // First, get the size of device list data 
    status = clGetContextInfo(context, 
                              CL_CONTEXT_DEVICES, 
                              0, 
                              NULL, 
                              &deviceListSize);
    if(status != CL_SUCCESS) 
	{  
		printf(
			"Error: Getting Context Info \
		    (device list size, clGetContextInfo)\n");
		printError(status, "clGetContextInfo");
		return;
	}

	// Detect OpenCL devices
    devices = (cl_device_id *)malloc(deviceListSize);
	if(devices == 0)
	{
		printf("Error: No devices found.\n");
		return;
	}

    // Now, get the device list data 
    status = clGetContextInfo(
			     context, 
                 CL_CONTEXT_DEVICES, 
                 deviceListSize, 
                 devices, 
                 NULL);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Getting Context Info \
		    (device list, clGetContextInfo)\n");
		printError(status, "clGetContextInfo");
		return;
	}

	// Create an OpenCL command queue
    commandQueue = clCreateCommandQueue(
					   context, 
                       devices[0], 
                       0, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Creating Command Queue. (clCreateCommandQueue)\n");
		printError(status, "clCreateCommandQueue");
		return;
	}

	// Create OpenCL memory buffers
	mtConfigsBuffer = clCreateBuffer(
						context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(MersenneTwisterConfig) * width,
                       mtConfigs, 
                       &status);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}
	// Create OpenCL memory buffers
    outputBuffer = clCreateBuffer(
					   context, 
                       CL_MEM_READ_WRITE | CL_MEM_USE_HOST_PTR,
                       sizeof(cl_float) * width,
                       randomValue, 
                       &status);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: clCreateBuffer\n");
		printError(status, "clCreateBuffer");
		return;
	}
	
	// Load CL file, build CL program object, create CL kernel object
    const char * filename  = "MersenneTwister.cl";
    string  sourceStr = convertToString(filename);
    const char * source    = sourceStr.c_str();
    size_t sourceSize[]    = { strlen(source) };

    program = clCreateProgramWithSource(
			      context, 
                  1, 
                  &source,
				  sourceSize,
                  &status);
	if(status != CL_SUCCESS) 
	{ 
	    printf("Error: Loading Binary into cl_program.\n");
		printError(status, "clCreateProgramWithBinary");
	  return;
	}

    // create a cl program executable for all the devices specified 
    status = clBuildProgram(program, 1, devices, NULL, NULL, NULL);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Building Program\n");
		printError(status, "clBuildProgram");
		return; 
	}

    // get a kernel object handle for a kernel with the given name 
    kernel = clCreateKernel(program, "TestMersenneTwisters", &status);
    if(status != CL_SUCCESS) 
	{  
		printf("Error: Creating Kernel from program.\n");
		printError(status, "clCreateKernel");
		return;
	}
}


// this is called once the OpenCL context, memory etc. are set up,
// the program is loaded into memory and the kernel handles are ready.
// It sets the values for kernels' arguments and enqueues calls to the kernels
// on to the command queue and waits till the calls have finished execution.
// It also gets kernel start and end time if profiling is enabled.
void MersenneTwister::runCLKernels(void)
{
	cl_int   status;
    cl_event events[2];
    size_t globalThreads[1];
    size_t localThreads[1];

    
    globalThreads[0] = width;
    localThreads[0]  = 1;

    // === Set appropriate arguments to the kernel ===
	status = clSetKernelArg(
					kernel, 
					0,				
					sizeof(cl_mem), 
					(void *)&mtConfigsBuffer);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: mtConfigsBuffer");
		return;
	}
    // the output array to the kernel 
    status = clSetKernelArg(
                    kernel, 
                    1,				// index of argument of kernel function
                    sizeof(cl_mem), 
                    (void *)&outputBuffer);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: outputBuffer");
		return;
	}

	status = clSetKernelArg(
					kernel, 
					2,				// index of argument of kernel function
					sizeof(cl_uint), 
					(void *)&width);
	if(status != CL_SUCCESS) 
	{ 
		printf("Error: Setting kernel argument.\n");
		printError(status, "clSetKernelArg: width");
		return;
	}

    // Enqueue a kernel run call.
    status = clEnqueueNDRangeKernel(
			     commandQueue,
                 kernel,
                 1,
                 NULL,
                 globalThreads,
                 localThreads,
                 0,
                 NULL,
                 &events[0]);
    if(status != CL_SUCCESS) 
	{ 
		printf(
			"Error: Enqueueing kernel onto command queue.\n");
		printError(status, "clEnqueueNDRangeKernel");
		return;
	}

    // wait for the kernel call to finish execution
    status = clWaitForEvents(1, &events[0]);
    if(status != CL_SUCCESS) 
	{ 
		printf(
		    "Error: Waiting for kernel run to finish.\n");
		printError(status, "clWaitForEvents");
		return;
	}

    status = clReleaseEvent(events[0]);
    if(status != CL_SUCCESS)
    {
        printf("Error: clReleaseEvent.\n");
		printError(status, "events[0]");
        return;
    }

    // Enqueue readBuffer
    status = clEnqueueReadBuffer(
                commandQueue,
                outputBuffer,
                CL_TRUE,
                0,
                width * sizeof(cl_float),
                randomValue,
                0,
                NULL,
                &events[1]);
    
    if(status != CL_SUCCESS) 
	{
        printf("Error: clEnqueueReadBuffer failed.\n");
		printError(status, "clEnqueueReadBuffer");
    }
	
    // Wait for the read buffer to finish execution
    status = clWaitForEvents(1, &events[1]);
    if(status != CL_SUCCESS) 
	{ 
		printf("Error: Waiting for read buffer call to finish.\n");
		printError(status, "clWaitForEvents");
		return;
	}
    
    status = clReleaseEvent(events[1]);
    if(status != CL_SUCCESS)
    {
        printf("Error: clReleaseEvent.\n");
		printError(status, "events[1]");
        return;
    }
}

void MersenneTwister::cleanupCL(void)
{
	cl_int status;

    status = clReleaseKernel(kernel);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseKernel \n");
		return; 
	}
    status = clReleaseProgram(program);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseProgram\n");
		return; 
	}
    
	status = clReleaseMemObject(outputBuffer);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseMemObject (outputBuffer)\n");
		return; 
	}

	status = clReleaseMemObject(mtConfigsBuffer);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseMemObject (mtConfigsBuffer)\n");
		return; 
	}
	
	status = clReleaseCommandQueue(commandQueue);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseCommandQueue\n");
		return;
	}
    status = clReleaseContext(context);
    if(status != CL_SUCCESS)
	{
		printf("Error: In clReleaseContext\n");
		return;
	}
}

void MersenneTwister::cleanupHost(void)
{
	if(randomValue != NULL)
	{
		free(randomValue);
		randomValue = NULL;
	}
	if(mtConfigs != NULL)
	{
		free(mtConfigs);
		mtConfigs = NULL;
	}
    if(devices != NULL)
    {
        free(devices);
        devices = NULL;
    }	
}

void MersenneTwister::printError(cl_int status, char * nameFunction)
{
	// print name of error
	cout << "Name of function: " << nameFunction << "; Errors code: ";

	switch(status)
	{
	case CL_DEVICE_NOT_FOUND:
		cout << "CL_DEVICE_NOT_FOUND" << endl;
		break;
	case CL_DEVICE_NOT_AVAILABLE:
		cout << "CL_DEVICE_NOT_AVAILABLE" << endl;
		break;
	case CL_COMPILER_NOT_AVAILABLE:
		cout << "CL_COMPILER_NOT_AVAILABLE" << endl;
		break;
	case CL_MEM_OBJECT_ALLOCATION_FAILURE:
		cout << "CL_MEM_OBJECT_ALLOCATION_FAILURE" << endl;
		break;
	case CL_OUT_OF_RESOURCES:
		cout << "CL_OUT_OF_RESOURCES" << endl;
		break;
	case CL_OUT_OF_HOST_MEMORY:
		cout << "CL_OUT_OF_HOST_MEMORY" << endl;
		break;
	case CL_PROFILING_INFO_NOT_AVAILABLE:
		cout << "CL_PROFILING_INFO_NOT_AVAILABLE" << endl;
		break;
	case CL_MEM_COPY_OVERLAP:
		cout << "CL_MEM_COPY_OVERLAP" << endl;
		break;
	case CL_IMAGE_FORMAT_MISMATCH:
		cout << "CL_IMAGE_FORMAT_MISMATCH" << endl;
		break;
	case CL_IMAGE_FORMAT_NOT_SUPPORTED:
		cout << "CL_IMAGE_FORMAT_NOT_SUPPORTED" << endl;
		break;
	case CL_BUILD_PROGRAM_FAILURE:
		cout << "CL_BUILD_PROGRAM_FAILURE" << endl;
		break;
	case CL_MAP_FAILURE:
		cout << "CL_MAP_FAILURE" << endl;
		break;
	
	case CL_INVALID_VALUE:
		cout << "CL_INVALID_VALUE" << endl;
		break;
	case CL_INVALID_DEVICE_TYPE:
		cout << "CL_INVALID_DEVICE_TYPE" << endl;
		break;
	case CL_INVALID_PLATFORM:
		cout << "CL_INVALID_PLATFORM" << endl;
		break;
	case CL_INVALID_DEVICE:
		cout << "CL_INVALID_DEVICE" << endl;
		break;
	case CL_INVALID_CONTEXT:
		cout << "CL_INVALID_CONTEXT" << endl;
		break;
	case CL_INVALID_QUEUE_PROPERTIES:
		cout << "CL_INVALID_QUEUE_PROPERTIES" << endl;
		break;
	case CL_INVALID_COMMAND_QUEUE:
		cout << "CL_INVALID_COMMAND_QUEUE" << endl;
		break;
	case CL_INVALID_HOST_PTR:
		cout << "CL_INVALID_HOST_PTR" << endl;
		break;
	case CL_INVALID_MEM_OBJECT:
		cout << "CL_INVALID_MEM_OBJECT" << endl;
		break;
	case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR:
		cout << "CL_INVALID_IMAGE_FORMAT_DESCRIPTOR" << endl;
		break;
	case CL_INVALID_IMAGE_SIZE:
		cout << "CL_INVALID_IMAGE_SIZE" << endl;
		break;
	case CL_INVALID_SAMPLER:
		cout << "CL_INVALID_SAMPLER" << endl;
		break;
	case CL_INVALID_BINARY:
		cout << "CL_INVALID_BINARY" << endl;
		break;
	case CL_INVALID_BUILD_OPTIONS:
		cout << "CL_INVALID_BUILD_OPTIONS" << endl;
		break;
	case CL_INVALID_PROGRAM:
		cout << "CL_INVALID_PROGRAM" << endl;
		break;
	case CL_INVALID_PROGRAM_EXECUTABLE:
		cout << "CL_INVALID_PROGRAM_EXECUTABLE" << endl;
		break;
	case CL_INVALID_KERNEL_NAME:
		cout << "CL_INVALID_KERNEL_NAME" << endl;
		break;
	case CL_INVALID_KERNEL_DEFINITION:
		cout << "CL_INVALID_KERNEL_DEFINITION" << endl;
		break;
	case CL_INVALID_KERNEL:
		cout << "CL_INVALID_KERNEL" << endl;
		break;
	case CL_INVALID_ARG_INDEX:
		cout << "CL_INVALID_ARG_INDEX" << endl;
		break;
	case CL_INVALID_ARG_VALUE:
		cout << "CL_INVALID_ARG_VALUE" << endl;
		break;
	case CL_INVALID_ARG_SIZE:
		cout << "CL_INVALID_ARG_SIZE" << endl;
		break;
	case CL_INVALID_KERNEL_ARGS:
		cout << "CL_INVALID_KERNEL_ARGS" << endl;
		break;
	case CL_INVALID_WORK_DIMENSION:
		cout << "CL_INVALID_WORK_DIMENSION" << endl;
		break;
	case CL_INVALID_WORK_GROUP_SIZE:
		cout << "CL_INVALID_WORK_GROUP_SIZE" << endl;
		break;
	case CL_INVALID_WORK_ITEM_SIZE:
		cout << "CL_INVALID_WORK_ITEM_SIZE" << endl;
		break;
	case CL_INVALID_GLOBAL_OFFSET:
		cout << "CL_INVALID_GLOBAL_OFFSET" << endl;
		break;
	case CL_INVALID_EVENT_WAIT_LIST:
		cout << "CL_INVALID_EVENT_WAIT_LIST" << endl;
		break;
	case CL_INVALID_EVENT:
		cout << "CL_INVALID_EVENT" << endl;
		break;
	case CL_INVALID_OPERATION:
		cout << "CL_INVALID_OPERATION" << endl;
		break;
	case CL_INVALID_GL_OBJECT:
		cout << "CL_INVALID_GL_OBJECT" << endl;
		break;
	case CL_INVALID_BUFFER_SIZE:
		cout << "CL_INVALID_BUFFER_SIZE" << endl;
		break;
	case CL_INVALID_MIP_LEVEL:
		cout << "CL_INVALID_MIP_LEVEL" << endl;
		break;
	case CL_INVALID_GLOBAL_WORK_SIZE:
		cout << "CL_INVALID_GLOBAL_WORK_SIZE" << endl;
		break;
	default:
		break;
	}

	// print log information
	char programLog[1024];
	status = clGetProgramBuildInfo(program, devices[0], CL_PROGRAM_BUILD_LOG, 1024, programLog, 0);
	cout << programLog << endl;
}

void MersenneTwister::printResult()
{
	cout << "Count: " << width << endl;
	for(unsigned int i=0; i<width; i++)
	{
		cout << randomValue[i] << " ";
	}
	cout << endl;
}